package com.benjaminwan.read.free.service

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.bluetooth.*
import android.bluetooth.BluetoothProfile.ServiceListener
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.*
import android.util.SparseArray
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import com.benjaminwan.read.free.R
import com.benjaminwan.read.free.common.ACTION_START_REQUEST_CODE
import com.benjaminwan.read.free.common.ACTION_START_SCRIPT
import com.benjaminwan.read.free.common.ACTION_STOP_REQUEST_CODE
import com.benjaminwan.read.free.common.ACTION_STOP_SCRIPT
import com.benjaminwan.read.free.lib.HidDefinition
import com.benjaminwan.read.free.lib.HidDefinition.BATTERY_REPORT_ID
import com.benjaminwan.read.free.lib.HidDefinition.BOOT_KEYBOARD_REPORT_ID
import com.benjaminwan.read.free.lib.HidDefinition.BOOT_MOUSE_REPORT_ID
import com.benjaminwan.read.free.lib.HidDefinition.DESCRIPTION
import com.benjaminwan.read.free.lib.HidDefinition.GAME_PAD_REPORT_ID
import com.benjaminwan.read.free.lib.HidDefinition.KEYBOARD_INPUT_REPORT_ID
import com.benjaminwan.read.free.lib.HidDefinition.KEYBOARD_LED_CAPS_LOCK
import com.benjaminwan.read.free.lib.HidDefinition.KEYBOARD_LED_NUM_LOCK
import com.benjaminwan.read.free.lib.HidDefinition.KEYBOARD_LED_SCROLL_LOCK
import com.benjaminwan.read.free.lib.HidDefinition.KEYBOARD_OUTPUT_REPORT_ID
import com.benjaminwan.read.free.lib.HidDefinition.MOUSE_REPORT_ID
import com.benjaminwan.read.free.lib.HidDefinition.NAME
import com.benjaminwan.read.free.lib.HidDefinition.PROVIDER
import com.benjaminwan.read.free.model.action.*
import com.benjaminwan.read.free.utils.HIDUtils.runAction
import com.benjaminwan.read.free.utils.setScanMode
import com.orhanobut.logger.Logger
import java.nio.ByteBuffer
import java.util.*
import java.util.concurrent.Executors

class HidWrapperService : Service() {
    private val mBinder: IBinder = LocalBinder()
    private val mAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
    private var mAppRegistered = false
    var mPluggedDevice: BluetoothDevice? = null
    private var mHidDevice: BluetoothHidDevice? = null
    private var mProtocol = BluetoothHidDevice.PROTOCOL_REPORT_MODE
    private var mBatteryLevel = 0f
    private var mCurrentBatteryLevel = 0f
    private var mConnectionState = BluetoothHidDevice.STATE_DISCONNECTED
    private val mMouseWrapper = MouseWrapper()
    private val mKeyboardWrapper = KeyboardWrapper()
    private val mBatteryWrapper = BatteryWrapper()
    private val mGamePadWrapper = GamePadWrapper()
    private var mEventListener: HidEventListener? = null
    private val mInputReportCache = SparseArray<ReportData>()
    private val mOutputReportCache = SparseArray<ReportData>()

    //执行线程
    private var scriptRunnable: ScriptRunnable
    private val scriptHandler: Handler

    init {
        val scriptHandlerThread = HandlerThread("ScriptHandler")
        scriptHandlerThread.start()
        scriptHandler = Handler(scriptHandlerThread.looper)
        scriptRunnable = ScriptRunnable()
    }

    interface HidEventListener {
        fun onApplicationState(registered: Boolean)
        fun onPluggedDeviceChanged(device: BluetoothDevice?)
        fun onConnectionState(device: BluetoothDevice?, connected: Boolean)
        fun onProtocolModeState(bootMode: Boolean)
        fun onKeyboardLedState(
            numLock: Boolean, capsLock: Boolean, scrollLock: Boolean
        )

        fun onRunningState(isRunning: Boolean)
        fun onRunningPos(pos: Position)
        fun onGroupProgress(progress: LoopProgress)
        fun onScriptProgress(progress: LoopProgress)
    }

    private data class ReportData constructor(
        var data: ByteArray = byteArrayOf()
    ) {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false

            other as ReportData

            if (!data.contentEquals(other.data)) return false

            return true
        }

        override fun hashCode(): Int {
            return data.contentHashCode()
        }
    }

    private val mServiceListener: ServiceListener = object : ServiceListener {
        override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
            Logger.i("Profile proxy connected")
            if (profile != BluetoothProfile.HID_DEVICE) {
                Logger.w("WTF? $profile")
                return
            }
            val btHid = proxy as BluetoothHidDevice
            if (btHid == null) {
                Logger.w("WTF? Proxy received but it's not BluetoothHidDevice")
                return
            }
            mHidDevice = btHid
            mHandler.obtainMessage(MESSAGE_REGISTER_APP).sendToTarget()
        }

        override fun onServiceDisconnected(profile: Int) {
            if (profile != BluetoothProfile.HID_DEVICE) return
            Logger.i("Profile proxy disconnected")

            // do not dereference proxy here as we won't be able to close it
        }
    }
    private val mCallback: BluetoothHidDevice.Callback = object : BluetoothHidDevice.Callback() {
        override fun onAppStatusChanged(
            pluggedDevice: BluetoothDevice?,
            registered: Boolean
        ) {
            super.onAppStatusChanged(pluggedDevice, registered)
            Logger.i(
                "onAppStatusChanged: pluggedDevice=$pluggedDevice registered=${registered}"
            )
            mEventListener?.onApplicationState(registered)
            if (registered) {
                this@HidWrapperService.mPluggedDevice = pluggedDevice
            } else {
                this@HidWrapperService.mPluggedDevice = null
            }
            mEventListener?.onPluggedDeviceChanged(this@HidWrapperService.mPluggedDevice)
        }

        override fun onConnectionStateChanged(
            device: BluetoothDevice,
            state: Int
        ) {
            val stateStr = when (state) {
                BluetoothProfile.STATE_CONNECTING -> "STATE_CONNECTING"
                BluetoothProfile.STATE_CONNECTED -> "STATE_CONNECTED"
                BluetoothProfile.STATE_DISCONNECTING -> "STATE_DISCONNECTING"
                BluetoothProfile.STATE_DISCONNECTED -> "STATE_DISCONNECTED"
                else -> "Unknow State"
            }
            Logger.i(stateStr)
            mConnectionState = state
            if (state == BluetoothHidDevice.STATE_CONNECTED) {
                mEventListener?.onConnectionState(device, true)
                mHandler.obtainMessage(MESSAGE_DEV_CONNECTED).sendToTarget()
                if (device != this@HidWrapperService.mPluggedDevice) {
                    this@HidWrapperService.mPluggedDevice = device
                    mEventListener?.onPluggedDeviceChanged(this@HidWrapperService.mPluggedDevice)
                }
            } else {
                /* Reset Protocol back to report Protocol Mode */
                mProtocol = BluetoothHidDevice.PROTOCOL_REPORT_MODE
                mEventListener?.onConnectionState(device, false)
            }
        }

        override fun onInterruptData(
            device: BluetoothDevice,
            reportId: Byte,
            data: ByteArray
        ) {
            Logger.i(
                "intr data: reportId=$reportId data=" + Arrays.toString(
                    data
                )
            )
            mHandler.obtainMessage(
                MESSAGE_INTR_DATA_RECEIVED,
                reportId.toInt(),
                0,
                ByteBuffer.wrap(data)
            )
                .sendToTarget()
        }

        override fun onSetProtocol(
            device: BluetoothDevice,
            protocol: Byte
        ) {
            Logger.i("protocol set to $protocol")
            mProtocol = protocol
            mEventListener!!.onProtocolModeState(protocol == BluetoothHidDevice.PROTOCOL_BOOT_MODE)
        }

        override fun onVirtualCableUnplug(device: BluetoothDevice) {
            this@HidWrapperService.mPluggedDevice = null
            mEventListener?.onPluggedDeviceChanged(this@HidWrapperService.mPluggedDevice)
        }

        override fun onGetReport(
            device: BluetoothDevice,
            type: Byte,
            id: Byte,
            bufferSize: Int
        ) {
            if (type == BluetoothHidDevice.REPORT_TYPE_INPUT) {
                mHandler.obtainMessage(
                    MESSAGE_GET_REPORT_RECEIVED,
                    id.toInt(),
                    bufferSize
                ).sendToTarget()
            } else if (type == BluetoothHidDevice.REPORT_TYPE_OUTPUT) {
                if (id != mKeyboardWrapper.getReportId(BluetoothHidDevice.REPORT_TYPE_OUTPUT.toInt())) {
                    Logger.i("onGetReport(), output report for invalid id = $id")
                    mHidDevice!!.reportError(device, BluetoothHidDevice.ERROR_RSP_INVALID_RPT_ID)
                } else {
                    val rd = mOutputReportCache[id.toInt()]
                    if (rd == null) {
                        /* No output report received with particular report id, report default
                         * values */
                        val mBuffer = ByteArray(8)
                        Logger.i("get_report id for keyboard")
                        for (i in 0..7) {
                            mBuffer[i] = 0x00
                        }
                        mHidDevice!!.replyReport(
                            device,
                            BluetoothHidDevice.REPORT_TYPE_OUTPUT,
                            id,
                            mBuffer
                        )
                    } else {
                        mHidDevice!!.replyReport(
                            device,
                            BluetoothHidDevice.REPORT_TYPE_OUTPUT,
                            id,
                            rd.data
                        )
                    }
                }
            } else {
                Logger.i("onGetReport(), unsupported report type = $type")
                /* Unsupported report type */mHidDevice!!.reportError(
                    device,
                    BluetoothHidDevice.ERROR_RSP_UNSUPPORTED_REQ
                )
            }
        }

        override fun onSetReport(
            device: BluetoothDevice,
            type: Byte,
            id: Byte,
            data: ByteArray
        ) {
            Logger.i("onSetReport(), type = " + type + " ,id=" + id + ", data=" + data.size)
            if (type != BluetoothHidDevice.REPORT_TYPE_OUTPUT) {
                /* Unsupported report type */
                Logger.i("onSetReport(), unsupported report type = $type")
                mHidDevice!!.reportError(device, BluetoothHidDevice.ERROR_RSP_UNSUPPORTED_REQ)
            } else {
                if (id != mKeyboardWrapper.getReportId(BluetoothHidDevice.REPORT_TYPE_OUTPUT.toInt())) {
                    Logger.i("onSetReport(), output report for invalid id = $id")
                    mHidDevice!!.reportError(device, BluetoothHidDevice.ERROR_RSP_INVALID_RPT_ID)
                } else {
                    mHandler.obtainMessage(
                        MESSAGE_SET_REPORT_RECEIVED, id.toInt(), 0,
                        ByteBuffer.wrap(data)
                    ).sendToTarget()
                }
            }
        }
    }

    private val mHandler: Handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MESSAGE_REGISTER_APP -> {
                    val sdp = BluetoothHidDeviceAppSdpSettings(
                        NAME, DESCRIPTION, PROVIDER,
                        BluetoothHidDevice.SUBCLASS1_COMBO, HidDefinition.DESCRIPTOR
                    )
                    val inQos = BluetoothHidDeviceAppQosSettings(
                        BluetoothHidDeviceAppQosSettings.SERVICE_GUARANTEED, 800, 9, 0,
                        11250 /* 10 ms */, BluetoothHidDeviceAppQosSettings.MAX /* 10 ms */
                    )
                    val outQos = BluetoothHidDeviceAppQosSettings(
                        BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT, 800, 9, 0,
                        11250 /* 10 ms */, BluetoothHidDeviceAppQosSettings.MAX /* 10 ms */
                    )
                    val result = mHidDevice?.registerApp(
                        sdp, inQos, outQos,
                        Executors.newCachedThreadPool(), mCallback
                    )
                    Logger.i("registerApp()=$result")
                    mAdapter?.setScanMode(
                        BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, 300000
                    )
                }
                MESSAGE_DEV_CONNECTED -> {
                    mInputReportCache.clear()
                    mOutputReportCache.clear()
                    mBatteryWrapper.update(mBatteryLevel)
                    mCurrentBatteryLevel = mBatteryLevel
                    mEventListener!!.onProtocolModeState(
                        mProtocol == BluetoothHidDevice.PROTOCOL_BOOT_MODE
                    )
                }
                MESSAGE_INTR_DATA_RECEIVED -> {
                    val reportId = msg.arg1.toByte()
                    val data = (msg.obj as ByteBuffer).array()
                    mKeyboardWrapper.parseIncomingReport(reportId, data, mEventListener)
                }
                MESSAGE_GET_REPORT_RECEIVED -> {
                    val id = msg.arg1.toByte()
                    val size = msg.arg2
                    var rd = mInputReportCache[id.toInt()]
                    if (rd != null) {
                        mHidDevice?.replyReport(
                            this@HidWrapperService.mPluggedDevice,
                            BluetoothHidDevice.REPORT_TYPE_INPUT,
                            id, rd.data
                        )
                    } else {
                        rd = ReportData()
                        /* Report id not in cache, send default values if id is valid */if (id == mKeyboardWrapper.getReportId(
                                BluetoothHidDevice.REPORT_TYPE_INPUT.toInt()
                            )
                        ) {
                            val mBuffer = ByteArray(8)
                            Logger.i("get_report id for keyboard")
                            var i = 0
                            while (i < 8) {
                                mBuffer[i] = 0x00
                                i++
                            }
                            storeReport(id, mBuffer, true)
                            mHidDevice?.replyReport(
                                this@HidWrapperService.mPluggedDevice,
                                BluetoothHidDevice.REPORT_TYPE_INPUT,
                                id,
                                mBuffer
                            )
                        } else if (id == mMouseWrapper.getReportId()) {
                            val mBuffer = ByteArray(4)
                            Logger.i("get_report id for mouse")
                            var i = 0
                            while (i < 4) {
                                mBuffer[i] = 0x00
                                i++
                            }
                            storeReport(id, mBuffer, true)
                            mHidDevice?.replyReport(
                                this@HidWrapperService.mPluggedDevice,
                                BluetoothHidDevice.REPORT_TYPE_INPUT,
                                id,
                                mBuffer
                            )
                        } else {
                            /* Invalid Report Id */
                            Logger.i("Get Report for Invalid report id = $id")
                            mHidDevice?.reportError(
                                this@HidWrapperService.mPluggedDevice,
                                BluetoothHidDevice.ERROR_RSP_INVALID_RPT_ID
                            )
                        }
                    }
                }
                MESSAGE_SET_REPORT_RECEIVED -> {
                    val rptId = msg.arg1.toByte()
                    val setRptData = (msg.obj as ByteBuffer).array()
                    Logger.i("MESSAGE_SET_REPORT_RECEIVED for id = $rptId")
                    if (rptId != mKeyboardWrapper.getReportId(BluetoothHidDevice.REPORT_TYPE_OUTPUT.toInt())) {
                        Logger.i("onSetReport(), set report for invalid id = $rptId")
                        mHidDevice?.reportError(
                            this@HidWrapperService.mPluggedDevice,
                            BluetoothHidDevice.ERROR_RSP_INVALID_RPT_ID
                        )
                    }
                    if (!mKeyboardWrapper.parseIncomingReport(rptId, setRptData, mEventListener)) {
                        Logger.i("onSetReport(), parameters invalid")
                        mHidDevice?.reportError(
                            this@HidWrapperService.mPluggedDevice,
                            BluetoothHidDevice.ERROR_RSP_INVALID_PARAM
                        )
                    }
                    Logger.i("onSetReport(), sending successful handshake for set report")
                    mHidDevice?.reportError(
                        this@HidWrapperService.mPluggedDevice,
                        BluetoothHidDevice.ERROR_RSP_SUCCESS
                    )
                }
            }
        }
    }
    private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(
            context: Context,
            intent: Intent
        ) {
            val action = intent.action
            if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
                val state =
                    intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
                if (state == BluetoothAdapter.STATE_ON) {
                    registerApp()
                } else {
                    unregisterApp()
                }
            } else if (action == Intent.ACTION_BATTERY_CHANGED) {
                val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
                val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)
                mBatteryLevel = level.toFloat() / scale.toFloat()
                if (mCurrentBatteryLevel != mBatteryLevel) {
                    Logger.i("ACTION_BATTERY_CHANGED, mBatteryLevel = $mBatteryLevel")
                    mBatteryWrapper.update(mBatteryLevel)
                    mCurrentBatteryLevel = mBatteryLevel
                }
            }
        }
    }

    private inner class MouseWrapper {
        private val mBuffer = ByteArray(4)

        fun getReportId(): Byte {
            return if (mProtocol == BluetoothHidDevice.PROTOCOL_BOOT_MODE) {
                BOOT_MOUSE_REPORT_ID
            } else {
                MOUSE_REPORT_ID
            }
        }

        @Synchronized
        fun move(dx: Byte, dy: Byte) {
            // leave buttons state unchanged
            mBuffer[1] = dx
            mBuffer[2] = dy
            val id = getReportId()
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)
        }

        @Synchronized
        fun buttonDown(which: Int) {
            mBuffer[0] = (mBuffer[0].toInt() or (1 shl which)).toByte()
            mBuffer[1] = 0
            mBuffer[2] = 0
            val id = getReportId()
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)
        }

        @Synchronized
        fun buttonUp(which: Int) {
            mBuffer[0] = (mBuffer[0].toInt() and (1 shl which).inv()).toByte()
            mBuffer[1] = 0
            mBuffer[2] = 0
            val id = getReportId()
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)
        }

        @Synchronized
        fun scroll(delta: Byte) {
            mBuffer[1] = 0
            mBuffer[2] = 0
            mBuffer[3] = delta
            val id = getReportId()
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)

            // reset to 0 after sending so it won't self-repeat in subsequent
            // reports
            mBuffer[3] = 0x00
        }
    }

    private inner class KeyboardWrapper {
        private val mBuffer = ByteArray(8)
        fun getReportId(type: Int): Byte {
            return if (mProtocol == BluetoothHidDevice.PROTOCOL_BOOT_MODE) {
                BOOT_KEYBOARD_REPORT_ID
            } else {
                if (type == BluetoothHidDevice.REPORT_TYPE_INPUT.toInt()) {
                    KEYBOARD_INPUT_REPORT_ID
                } else {
                    KEYBOARD_OUTPUT_REPORT_ID
                }
            }
        }

        @Synchronized
        fun keyDown(key: Byte) {
            if (key >= Companion.MODIFIER_BASE && key <= MODIFIER_BASE + MODIFIER_COUNT) {
                mBuffer[0] =
                    (mBuffer[0].toInt() or (1 shl key - MODIFIER_BASE)).toByte()
            } else if (key.toInt() and 0x80 != 0) {
                mBuffer[1] = (mBuffer[1].toInt() or (1 shl (key.toInt() and 0x07))).toByte()
            } else {
                for (i in 2..7) {
                    if (mBuffer[i] == 0x00.toByte()) {
                        mBuffer[i] = key
                        break
                    }
                }
            }
            val id = getReportId(BluetoothHidDevice.REPORT_TYPE_INPUT.toInt())
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)
        }

        @Synchronized
        fun keyUp(key: Byte) {
            if (key >= Companion.MODIFIER_BASE && key <= Companion.MODIFIER_BASE + Companion.MODIFIER_COUNT) {
                mBuffer[0] =
                    (mBuffer[0].toInt() and (1 shl key - Companion.MODIFIER_BASE).inv()).toByte()
            } else if (key.toInt() and 0x80 != 0) {
                mBuffer[1] = (mBuffer[1].toInt() and (1 shl (key.toInt() and 0x07)).inv()).toByte()
            } else {
                for (i in 2..7) {
                    if (mBuffer[i] == key) {
                        mBuffer[i] = 0x00
                        break
                    }
                }
            }
            val id = getReportId(BluetoothHidDevice.REPORT_TYPE_INPUT.toInt())
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)
        }

        fun parseIncomingReport(
            reportId: Byte,
            data: ByteArray,
            callback: HidEventListener?
        ): Boolean {
            Logger.i("parseIncomingReport")
            if (reportId != getReportId(BluetoothHidDevice.REPORT_TYPE_OUTPUT.toInt()) || callback == null) return false
            Logger.i("parseIncomingReport(): data.length = " + data.size + " mProtocol = " + mProtocol)

            /* Output report can only be of 1 byte */if (data.size != 1) return false
            val leds = data[0].toInt()
            storeReport(reportId, data, false)
            val isNumLock = leds and KEYBOARD_LED_NUM_LOCK != 0
            val isCapsLock = leds and KEYBOARD_LED_CAPS_LOCK != 0
            val isScrollLock = leds and KEYBOARD_LED_SCROLL_LOCK != 0
            Logger.i("isNumLock=$isNumLock ,isCapsLock=$isCapsLock ,isScrollLock=$isScrollLock")
            callback.onKeyboardLedState(isNumLock, isCapsLock, isScrollLock)
            return true
        }

    }

    private inner class BatteryWrapper {
        private val mBuffer = ByteArray(1)

        @Synchronized
        fun update(level: Float) {
            Logger.i("BatteryWrapper: update: level = $level")
            if (mConnectionState != BluetoothHidDevice.STATE_CONNECTED) {
                // battery update will not trigger reconnection
                return
            }
            // battery strength not supported in boot mode
            if (mProtocol != BluetoothHidDevice.PROTOCOL_REPORT_MODE) return
            val batteryVal = (level * 255).toInt()
            Logger.i("BatteryWrapper: update: val = $batteryVal")
            mBuffer[0] = (batteryVal and 0xff).toByte()
            Logger.i("BatteryWrapper: update: mBuffer[0] = " + mBuffer[0])
            if (mHidDevice != null) {
                storeReport(BATTERY_REPORT_ID, mBuffer, true)
                mHidDevice?.sendReport(
                    this@HidWrapperService.mPluggedDevice,
                    BATTERY_REPORT_ID.toInt(),
                    mBuffer
                )
            }
        }
    }

    private inner class GamePadWrapper {
        private val mBuffer = ByteArray(3)

        @Synchronized
        fun axis(dx: Byte, dy: Byte) {
            // leave buttons state unchanged
            mBuffer[0] = dx
            mBuffer[1] = dy
            val id = GAME_PAD_REPORT_ID
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)
        }

        @Synchronized
        fun buttonDown(which: Int) {
            mBuffer[2] = (mBuffer[2].toInt() or (1 shl which)).toByte()
            val id = GAME_PAD_REPORT_ID
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)
        }

        @Synchronized
        fun buttonUp(which: Int) {
            mBuffer[2] = (mBuffer[2].toInt() and (1 shl which).inv()).toByte()
            val id = GAME_PAD_REPORT_ID
            storeReport(id, mBuffer, true)
            mHidDevice?.sendReport(this@HidWrapperService.mPluggedDevice, id.toInt(), mBuffer)
        }
    }

    inner class LocalBinder : Binder() {
        val service: HidWrapperService
            get() = this@HidWrapperService
    }

    override fun onBind(intent: Intent): IBinder? {
        return mBinder
    }

    private lateinit var mNotificationManager: NotificationManager
    private val mAppGroupId = "group_read_free"
    private val mAppChannelId = "read_free"
    private val mAppChannelName = "ReadFree"
    private val mAppNotifyId = 21

    private val mMsgGroupId = "read_free_group"
    private val mMsgChannelId = "read_free_msg"
    private val mMsgChannelName = "蓝牙HID服务"
    private val mMsgNotifyId = 22

    override fun onCreate() {
        Logger.i("onCreate()")
        mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            //适配8.0service
            val appChannel = NotificationChannel(
                mAppChannelId,
                mAppChannelName,
                NotificationManager.IMPORTANCE_LOW
            )
            mNotificationManager.createNotificationChannel(appChannel)
            val appNotification = NotificationCompat.Builder(this, mAppChannelId)
                .setGroup(mAppGroupId)
                .build()
            startForeground(mAppNotifyId, appNotification)

            val msgChannel = NotificationChannel(
                mMsgChannelId,
                mMsgChannelName,
                NotificationManager.IMPORTANCE_HIGH
            )
            // 开启指示灯，如果设备有的话
            msgChannel.enableLights(true)
            // 设置指示灯颜色
            msgChannel.lightColor = Color.RED
            // 是否在久按桌面图标时显示此渠道的通知
            msgChannel.setShowBadge(true)
            // 设置是否应在锁定屏幕上显示此频道的通知
            msgChannel.lockscreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
            // 设置绕过免打扰模式
            msgChannel.setBypassDnd(true)
            //为当前渠道添加一个描述信息，用户在点击渠道进入详情页，会在最下面看到
            msgChannel.description = "ReadFree后台蓝牙HID服务"
            //振动
            msgChannel.enableVibration(true)
            msgChannel.vibrationPattern = longArrayOf(100, 200, 200, 200)
            mNotificationManager.createNotificationChannel(msgChannel)

            showControlNotification()
        }
        val filter = IntentFilter()
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
        filter.addAction(Intent.ACTION_BATTERY_CHANGED)
        registerReceiver(mReceiver, filter)
        registerApp()
    }

    private fun showControlNotification() {
        val remoteViews = RemoteViews(packageName, R.layout.notification)
        val startIntent = Intent()
        startIntent.action = ACTION_START_SCRIPT
        val pendingStartIntent =
            PendingIntent.getBroadcast(
                applicationContext, ACTION_START_REQUEST_CODE,
                startIntent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
        val stopIntent = Intent()
        stopIntent.action = ACTION_STOP_SCRIPT
        val pendingStopIntent = PendingIntent.getBroadcast(
            applicationContext, ACTION_STOP_REQUEST_CODE,
            stopIntent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        remoteViews.setOnClickPendingIntent(R.id.startBtn, pendingStartIntent)
        remoteViews.setOnClickPendingIntent(R.id.stopBtn, pendingStopIntent)
        val notification = NotificationCompat.Builder(this, mMsgChannelId)
            //.setLights(Color.RED, 1000, 1000)//添加LED灯闪烁，第一个1000是亮的ms，第二个是熄灭的ms，也就是亮一秒，灭一秒
            //.setVibrate(longArrayOf(100, 200, 200, 200))//振动
            .setCustomContentView(remoteViews)
            .setCustomBigContentView(remoteViews)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setOngoing(true)
            .setTicker("脚本运行控制")//通知首次出现在通知栏，带上升动画效果的
            .setLargeIcon(BitmapFactory.decodeResource(this.resources, R.mipmap.ic_app))
            .setSmallIcon(R.mipmap.ic_app)
            //.setContentTitle(getString(R.string.app_name))
            //.setContentIntent(pendingIntent)
            .setGroup(mMsgGroupId)
            .build()
        mNotificationManager.notify(mMsgNotifyId, notification)
    }

    private fun showCompleteNotification(){
        val notification = NotificationCompat.Builder(this, mMsgChannelId)
            //.setLights(Color.RED, 1000, 1000)//添加LED灯闪烁，第一个1000是亮的ms，第二个是熄灭的ms，也就是亮一秒，灭一秒
            //.setVibrate(longArrayOf(100, 200, 200, 200))//振动
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setTicker("脚本运行完毕!")//通知首次出现在通知栏，带上升动画效果的
            .setLargeIcon(BitmapFactory.decodeResource(this.resources, R.mipmap.ic_app))
            .setSmallIcon(R.mipmap.ic_app)
            .setContentTitle(getString(R.string.app_name))
            .setContentText("脚本运行完毕!")
            .setGroup(mMsgGroupId)
            .build()
        mNotificationManager.notify(23, notification)
    }

    private fun cancelNotification() {
        mNotificationManager.cancel(mMsgNotifyId)
    }

    override fun onDestroy() {
        Logger.i("onDestroy()")
        unregisterApp()
        unregisterReceiver(mReceiver)
        cancelNotification()
    }

    private fun registerApp() {
        mAdapter ?: return
        Logger.i("registerApp(): mAppRegistered=$mAppRegistered")
        if (mAppRegistered || !mAdapter.isEnabled) return
        if (!mAdapter.getProfileProxy(
                applicationContext, mServiceListener,
                BluetoothProfile.HID_DEVICE
            )
        ) {
            Logger.i("Cannot obtain profile proxy")
            return
        }
        mAppRegistered = true
    }

    private fun unregisterApp() {
        mAdapter ?: return
        Logger.i("unregisterApp(): mAppRegistered=$mAppRegistered")
        if (!mAppRegistered) return
        if (mHidDevice != null) {
            mHidDevice!!.unregisterApp()
            mAdapter.closeProfileProxy(BluetoothProfile.HID_DEVICE, mHidDevice)
        }
        mAppRegistered = false
    }

    fun setEventListener(listener: HidEventListener?) {
        mEventListener = listener
    }

    fun mouseMove(dx: Int, dy: Int) {
        if (mHidDevice == null) return
        //Logger.i("mouseMove: dx=$dx, dy=$dy")
        mMouseWrapper.move(dx.toByte(), dy.toByte())
    }

    fun mouseButtonDown(which: Int) {
        if (mHidDevice == null) return
        //Logger.i("mouseButtonDown: $which")
        mMouseWrapper.buttonDown(which)
    }

    fun mouseButtonUp(which: Int) {
        if (mHidDevice == null) return
        //Logger.i("mouseButtonUp: $which")
        mMouseWrapper.buttonUp(which)
    }

    fun mouseScroll(delta: Byte) {
        if (mHidDevice == null) return
        //Logger.i("mouseScroll: $delta")
        mMouseWrapper.scroll(delta)
    }

    fun gamePadAxis(dx: Int, dy: Int) {
        if (mHidDevice == null) return
        //Logger.i("gamePadAxis: $dx , $dy")
        mGamePadWrapper.axis(dx.toByte(), dy.toByte())
    }

    fun gamePadButtonDown(which: Int) {
        if (mHidDevice == null) return
        //Logger.i("gamePadButtonDown: $which")
        mGamePadWrapper.buttonDown(which)
    }

    fun gamePadButtonUp(which: Int) {
        if (mHidDevice == null) return
        //Logger.i("gamePadButtonUp: $which")
        mGamePadWrapper.buttonUp(which)
    }

    fun keyboardKeyDown(key: Byte) {
        if (mHidDevice == null) return
        //Logger.i("keyboardKeyDown: $key")
        mKeyboardWrapper.keyDown(key)
    }

    fun keyboardKeyUp(key: Byte) {
        if (mHidDevice == null) return
        //Logger.i("keyboardKeyUp: $key")
        mKeyboardWrapper.keyUp(key)
    }

    fun connect() {
        if (mHidDevice == null) return
        mHidDevice?.connect(this@HidWrapperService.mPluggedDevice)
    }

    fun disconnect() {
        if (mHidDevice == null) return
        mHidDevice?.disconnect(this@HidWrapperService.mPluggedDevice)
    }

    fun unplug() {
        if (mHidDevice == null) return
        unregisterApp()
        unregisterReceiver(mReceiver)
    }

    private fun storeReport(
        reportId: Byte,
        data: ByteArray,
        inputReport: Boolean
    ) {
        var rd: ReportData?
        rd = if (inputReport) {
            mInputReportCache[reportId.toInt()]
        } else {
            mOutputReportCache[reportId.toInt()]
        }
        if (rd == null) {
            rd = ReportData()
            if (inputReport) {
                mInputReportCache.put(reportId.toInt(), rd)
            } else {
                mOutputReportCache.put(reportId.toInt(), rd)
            }
        }
        rd.data = data.clone()
    }

    private fun runActions(idGroup: Int, actions: List<Action>, global: GlobalConfig) {
        actions.forEachIndexed { idAction, action ->
            if (!scriptRunnable.isRunningAction) return
            mEventListener?.onRunningPos(Position(idGroup, idAction))
            runAction(this, action, global)
        }
    }

    private fun runGroups(groups: List<Group>, global: GlobalConfig) {
        groups.forEachIndexed { idGroup, actionGroup ->
            if (!scriptRunnable.isRunningAction) return
            val loopCount = actionGroup.loopRange.random()
            if (loopCount > 0) {
                (1..loopCount).forEach { count ->
                    if (!scriptRunnable.isRunningAction) return
                    mEventListener?.onGroupProgress(LoopProgress(idGroup, count, loopCount))
                    runActions(idGroup, actionGroup.actions, global)
                }
            }
        }
    }

    inner class ScriptRunnable : Runnable {
        private var script: Script = Script()
        var isRunningAction: Boolean = false

        fun init(script: Script): ScriptRunnable {
            this.script = script
            isRunningAction = true
            mEventListener?.onRunningState(true)
            return this
        }

        fun stop() {
            isRunningAction = false
            mEventListener?.onRunningState(false)
            mEventListener?.onScriptProgress(LoopProgress(-1, -1, -1))
            mEventListener?.onGroupProgress(LoopProgress(-1, -1, -1))
        }

        override fun run() {
            val loopCount = script.globalConfig.loopRange.random()
            if (script.groups.isNotEmpty()) {
                if (loopCount > 0) {
                    (1..loopCount).forEach { count ->
                        if (!isRunningAction) return@forEach
                        mEventListener?.onScriptProgress(LoopProgress(-1, count, loopCount))
                        runGroups(script.groups, script.globalConfig)
                    }
                } else {
                    var count = 1
                    while (isRunningAction) {
                        mEventListener?.onScriptProgress(LoopProgress(-1, count, 0))
                        runGroups(script.groups, script.globalConfig)
                        count++
                    }
                }
            }
            showCompleteNotification()
            stop()
        }
    }

    fun startScript(script: Script) {
        scriptRunnable = ScriptRunnable()
        scriptRunnable.init(script)
        scriptHandler.post(scriptRunnable)
    }

    fun stopScript() {
        scriptRunnable.stop()
        scriptHandler.removeCallbacks(scriptRunnable)
    }

    companion object {
        private const val MESSAGE_REGISTER_APP = 1
        private const val MESSAGE_APP_REGISTERED = 2
        private const val MESSAGE_DEV_CONNECTED = 3
        private const val MESSAGE_INTR_DATA_RECEIVED = 4
        private const val MESSAGE_GET_REPORT_RECEIVED = 5
        private const val MESSAGE_SET_REPORT_RECEIVED = 6
        private const val MODIFIER_BASE = 0xe0.toByte()
        private const val MODIFIER_COUNT: Byte = 8 /* left ctrl -> right gui */
    }
}